package hu.elte.inf.holiuml.coordgeom;

/**
 * Represents an (infinite) line on the plane.
 * 
 * It is an immutable object.
 * @author Csaba Hoch
 */
public class Line {

	static final int MARGIN = 10;

	/**
	 * The direction vector of the line.
	 */
	private CGVector dirVector;

	/**
	 * A point that is on the line.
	 */
	private CGPoint p;

	/**
	 * The equation of the line is: a*x + b*y + c = 0
	 * 
	 * If (x,y) is on the "left" side of the line, then the following is true:
	 * a*x + b*y + c < 0
	 */
	private int a, b, c;

	/**
	 * Creates a new line based on two of its points.
	 */
	public Line(CGPoint p1, CGPoint p2) {
		this.p = p1;
		this.dirVector = new CGVector(p1, p2);
		calculate_abc(p2);
	}

	/**
	 * Creates a new line based on a point and a direction vector.
	 * 
	 * @param p
	 *            A point on the line.
	 * @param v
	 *            The direction vector of the line.
	 */
	public Line(CGPoint p, CGVector v) {
		this.p = p;
		this.dirVector = v;
		recalculate_abc();
	}

	/**
	 * Calculates the coefficients of the equation.
	 */
	private void recalculate_abc() {
		calculate_abc(p.addVector(dirVector));		
	}
	
	private void calculate_abc(CGPoint p2) {
		a = p2.getY() - p.getY();
		b = p.getX() - p2.getX();
		c = -a * p.getX() - b * p.getY();
	}

	/**
	 * Returns true is p is on the left side of the line (or if it is exactly on
	 * the line).
	 * 
	 * The line has a notion of left and right side. If you consider the
	 * direction vector on the following figure, it will be easy to see, why is
	 * left left and why is right right.
	 */
	// dirVector
	// ---->
	// 
	// p left
	// ---x------------>
	// right
	public boolean isOnTheLeft(CGPoint p) {
		return value(p) >= 0;
	}

	public boolean isOnTheRight(CGPoint p) {
		return value(p) <= 0;
	}

	/**
	 * Returns true if the given point is near to the line.
	 */
	public boolean isNear(CGPoint p) {
		return distanceFromPoint(p) <= MARGIN;
	}

	/**
	 * Calculates the point that is the intersection of the current line and the
	 * line in the argument list. Returns null if there is no intersection.
	 */
	public CGPoint intersection(Line l) {
		int d = l.a;
		int e = l.b;
		int f = l.c;

		int denom = (e * a - d * b);
		try {
			int x = (b * f - c * e) / denom;
			int y = (c * d - a * f) / denom;
			return new CGPoint(x, y);
		} catch (Exception exc) {
			return null;
		}
	}

	/**
	 * Calculates the distance between the line and the given point.
	 */
	public int distanceFromPoint(CGPoint p) {
		return (int) (Math.abs(a * p.getX() + b * p.getY() + c) / Math.sqrt(a
				* a + b * b));
	}

	/**
	 * Returns the value of the equation of the line applied to the given point.
	 */
	public int value(CGPoint p) {
		return a * p.getX() + b * p.getY() + c;
	}
	
	public CGPoint getPoint() {
		return p;
	}

	public CGVector getDirVector() {
		return dirVector;
	}

	public int getA() {
		return a;
	}

	public int getB() {
		return b;
	}

	public int getC() {
		return c;
	}
	
	/**
	 * Adds the given vector to the current line.
	 * 
	 * @return The new line.
	 */
	public Line addVector(CGVector v) {
		return new Line(p.addVector(v), dirVector);
	}
	
	/**
	 * Flips the current line, so its direction will be reversed.
	 * @return
	 */
	public Line getFlipped() {
		return new Line(p, dirVector.getFlipped());
	}
	
	public String toString() {
		return "Point: "+p+" DirVector="+dirVector+" a="+a+" b="+b+" c="+c;
	}
}
